package com.beyondsoft.sabg.edu.service.core.impl;

import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.LRUCache;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.hutool.jwt.JWTUtil;
import com.beyondsoft.sabg.edu.configure.sercurity.JwtTokenProvider;
import com.beyondsoft.sabg.edu.model.db.core.Privilege;
import com.beyondsoft.sabg.edu.model.db.core.Role;
import com.beyondsoft.sabg.edu.model.db.core.SystemSetting;
import com.beyondsoft.sabg.edu.model.db.core.UserAccount;
import com.beyondsoft.sabg.edu.model.dto.PrivilegeDTO;
import com.beyondsoft.sabg.edu.model.dto.UserAccountDTO;
import com.beyondsoft.sabg.edu.model.enums.Names;
import com.beyondsoft.sabg.edu.model.exception.GenericBusinessException;
import com.beyondsoft.sabg.edu.model.exception.InvalidParamException;
import com.beyondsoft.sabg.edu.model.tree.PrivilegeTree;
import com.beyondsoft.sabg.edu.model.vo.core.UserInfoVO;
import com.beyondsoft.sabg.edu.repository.core.PrivilegeRepo;
import com.beyondsoft.sabg.edu.repository.core.RoleRepo;
import com.beyondsoft.sabg.edu.repository.core.UserAccountRepo;
import com.beyondsoft.sabg.edu.service.core.UserAccountService;
import com.beyondsoft.sabg.edu.service.impl.AbstractSimpleServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.persistence.criteria.*;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author Mo cr
 * @Date 2023/8/4
 */
@Service
@Slf4j
public class UserAccountServiceImpl extends AbstractSimpleServiceImpl<UserAccount, UserAccountRepo> implements UserAccountService {
    @Autowired
    private UserAccountRepo userAccountRepo;
    @Autowired
    private JwtTokenProvider jwtTokenProvider;
    @Autowired
    private PrivilegeRepo privilegeRepo;
    @Autowired
    private RoleRepo roleRepo;

    private static LRUCache<String, String> tokenCache;
    @PostConstruct
    public void init(){
        tokenCache = CacheUtil.newLRUCache(1,7000 * 1000);
    }

    @Override
    public UserInfoVO getCurrentUser() {
        try {
            Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            if (principal instanceof UserDetails) {
                UserAccount userAccount = (UserAccount) principal;
                UserInfoVO userInfoVO = new UserInfoVO();
                userInfoVO.setId(userAccount.getId());
                userInfoVO.setUserName(userAccount.getUsername());
                userInfoVO.setName(userAccount.getName());
                userInfoVO.setRoles(userAccount.getRoles());
                userInfoVO.setToken(
                        jwtTokenProvider.generateToken(
                                userAccount.getUsername()));
                return userInfoVO;
            }
        } catch (Exception e) {
            log.error("can not get principal");
        }
        return null;
    }

    //自动注册， 不存在创建账户（默认role为学生）
    @Override
    public UserAccount autoRegister(UserAccount user) {
        Role role = roleRepo.findByIdAndValidity(1L, true).get();
        Set<Role> set = new HashSet<>();
        set.add(role);
        user.setRoles(set);
        return userAccountRepo.save(user);
    }


    @Override
    public UserAccountDTO login(UserAccountDTO userAccountDTO) {
        if (StrUtil.isBlank(userAccountDTO.getUsername())) {
            throw new InvalidParamException("user name is null");
        }

        if (StrUtil.isNotEmpty(userAccountDTO.getTicket())) {
            UserAccount userAccount = new UserAccount();
            UserAccount userFromDB = this.findByUsername(userAccountDTO.getUsername());
            if (userFromDB != null) {
                userAccount = userFromDB;
            }
            userAccount.setUsername(userAccountDTO.getUsername());
            return getUserAccountDTO(userAccount);
        }


        UserAccount userAccount = this.findByUsername(userAccountDTO.getUsername());
        if (userAccount == null) {
            throw new InvalidParamException("user is not exist");
        }
//        if (passwordEncoder.matches(userAccountDTO.getPassword(), userAccount.getPassword())) {
        if (userAccountDTO.getPassword().equals("Beyondsoft@2023")) {
            UserAccountDTO res = getUserAccountDTO(userAccount);
            return res;
        }
        throw new GenericBusinessException("login failed");
    }

    private UserAccountDTO getUserAccountDTO(UserAccount userAccount) {
        String token = jwtTokenProvider.generateToken(userAccount.getUsername());

        UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userAccount,
                "", userAccount.getAuthorities());

        SecurityContextHolder.getContext().setAuthentication(auth);
        UserAccountDTO res = new UserAccountDTO();
        res.setToken(token);
        res.setPassword(userAccount.getPassword());
        res.setUsername(userAccount.getUsername());
        res.setRoles(userAccount.getRoles());
        res.setName(userAccount.getName());
        return res;
    }

    @Override
    public UserAccount findByUsername(String userName) {
        return userAccountRepo.findByUsernameAndValidity(userName, true);
    }

    @Override
    public List<PrivilegeTree> getPrivilegeTree() {
        return getChildren(0L, privilegeRepo.findAllByValidity(true));
    }

    @Override
    public List<Long> getRolePrivilege(Long id) {
        List<Long> ids = new ArrayList<>();

        //角色的所有权限
        Set<Privilege> privileges = roleRepo.findByIdAndValidity(id, true).get().getPrivileges();

        List<PrivilegeTree> treeList = getChildren(0L, privileges.stream().collect(Collectors.toList()));
        getLeafNode(treeList, ids);

        return ids;
    }

    @Override
    public List<Role> getRoleList() {
        return roleRepo.findAllByValidity(true);
    }

    @Override
    public void savePrivilege(PrivilegeDTO privilegeDTO) {
        if (StrUtil.isEmpty(privilegeDTO.getRoleName()) || CollUtil.isEmpty(privilegeDTO.getPrivilegeIdList())) {
            throw new InvalidParamException("role name or privilege is invalid");
        }
        roleRepo.findByIdAndValidity(privilegeDTO.getRoleId(), true)
                .ifPresentOrElse(role -> {
                    saveRole(privilegeDTO, role);
                }, () -> {
                    Role role = new Role();
                    saveRole(privilegeDTO, role);
                });
    }

    private void saveRole(PrivilegeDTO privilegeDTO, Role role) {
        role.setPrivileges(
                new HashSet<>(privilegeRepo.findAllByIdInAndValidity(privilegeDTO.getPrivilegeIdList(), true)));
        role.setDescription(privilegeDTO.getRoleName());
        roleRepo.save(role);
    }

    @Override
    public void updateRole(UserAccountDTO userAccountDTO) {
        Set<Long> userIds = userAccountDTO.getUserIds();
        if (CollUtil.isEmpty(userIds)) {
            throw new GenericBusinessException("user id is null");
        }
        List<UserAccount> userAccountList = userAccountRepo.findAllById(userIds);
        List<UserAccount> changeRoleUserAccountList = userAccountList.stream().map(userAccount -> {
            userAccount.getRoles().clear();
            List<Role> allByIdInAndValidity = roleRepo.findAllByIdInAndValidity(userAccountDTO.getRoleIds(), true);
            userAccount.getRoles().addAll(allByIdInAndValidity);
            return userAccount;
        }).collect(Collectors.toList());
        userAccountRepo.saveAll(changeRoleUserAccountList);

    }

    @Override
    public void deleteRoleById(Long roleId) {
        if (roleId == null) {
            throw new GenericBusinessException("role id is required");
        }
        roleRepo.deleteById(roleId);
    }

    @Override
    public String getXxcappUser(String code) {
        return tryGetXxcappUser(code);
    }


    /**
     * 最多可重试一次
     *
     * @param code
     * @return
     */
    public synchronized String tryGetXxcappUser(String code) {
        try {
            HttpResponse response = HttpRequest.get("https://xxcapp.xidian.edu.cn/uc/api/oauth/user-by-code?code=" + code + "&access_token=" + getXxcappToken())
                    .execute();
            log.info("tryGetXxcappUser,code:{}, responseBody:{}",code, response.body());

            if(response.getStatus()!=200){
                tokenCache.put(Names.XXCAPP_TOKEN,"");
                log.error("tryGetXxcappUser failed,code:{}, response:{}",code, response);
                return null;
            }

            String body = response.body();
            JSONObject resJson = JSONUtil.parseObj(body);
            if(!String.valueOf(resJson.get("e")).equalsIgnoreCase("0")){
                tokenCache.put(Names.XXCAPP_TOKEN,"");
                log.error("tryGetXxcappUser failed,code:{}, response:{}",code, response);
                return null;
            }
            String number = String.valueOf(resJson.getJSONObject("d").getJSONObject("role").get("number"));

            return number;
        } catch (Exception e) {
            log.error("tryGetXxcappUser Exception,code:{},ex:{}",code,e.getMessage());
            return null;
        }
    }


    @Override
    public synchronized String getXxcappToken() {
        String xxcappToken = tokenCache.get(Names.XXCAPP_TOKEN);
        if (StrUtil.isBlank(xxcappToken)) {
            String body = HttpRequest.get("https://xxcapp.xidian.edu.cn/api/third/get-token?appid=200240308113838850&appsecret=5mqlzt5mxjhsnp85eme978udlvg9mdbn")
                    .execute().body();
            xxcappToken = String.valueOf(JSONUtil.parseObj(body).getJSONObject("d").get("access_token"));
            tokenCache.put(Names.XXCAPP_TOKEN,xxcappToken);
        }
        log.info("get xxcappToken success: {}", xxcappToken);
        return xxcappToken;
    }


    private List<PrivilegeTree> getChildren(Long id, List<Privilege> allPrivilege) {
        return allPrivilege.stream()
                .filter(p -> p.getParentId().equals(id))
                .map(p -> {
                    PrivilegeTree privilegeTree = new PrivilegeTree();
                    privilegeTree.setKey(p.getId());
                    privilegeTree.setTitle(p.getName());
                    privilegeTree.setCode(p.getCode());
                    privilegeTree.setType(p.getType());
                    privilegeTree.setPath(p.getPath());
                    privilegeTree.setParentId(p.getParentId());
                    privilegeTree.setChildren(getChildren(p.getId(), allPrivilege));
                    return privilegeTree;
                }).collect(Collectors.toList());
    }

    private void getLeafNode(List<PrivilegeTree> treeList, List<Long> ids) {
        treeList.forEach(node -> {
            if (node.hasChildren()) {
                getLeafNode(node.getChildren(), ids);
            } else {
                ids.add(node.getKey());
            }
        });

    }

    @Override
    protected void addConditions(Root<UserAccount> root, CriteriaBuilder criteriaBuilder, List<Predicate> predicates, Object o) {
        super.addConditions(root, criteriaBuilder, predicates, o);

        if (o instanceof UserAccountDTO) {
            UserAccountDTO userAccountDTO = (UserAccountDTO) o;
            if (StrUtil.isNotEmpty(userAccountDTO.getNameOrNumber())) {
                String nameOrNumber = userAccountDTO.getNameOrNumber();
                predicates.add(criteriaBuilder.or(
                        criteriaBuilder.like(root.get("name"), "%" + nameOrNumber + "%")
                        , criteriaBuilder.like(root.get("username"), "%" + nameOrNumber + "%")
                ));
            }
            if (CollUtil.isNotEmpty(userAccountDTO.getRoleIds())) {
                Join<Object, Object> role = root.join("roles", JoinType.LEFT);
                predicates.add(criteriaBuilder.in(role.get("id")).value(userAccountDTO.getRoleIds()));
            }
        }
    }
}

